package hyl.base.cache2;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

//import com.alibaba.fastjson.JSON;

import hyl.base.cache.ACache;
import hyl.base.cache.RedisConfig;

import hyl.core.MyFun;
import hyl.core.data.ACValue;
import hyl.core.data.ExpValue;
import hyl.core.data.IMap;
import hyl.core.reflect.MyKryo;
import hyl.core.run.IDo1;

/**
 * 过期的keyvalue 自动删除
 * 
 * 数据保存在 redis中
 * 
 * 对象不 计次和时间属性 不保存在redis中
 * 
 * 主要用于 构造 session 队列
 */
public class TimeCache<T> extends ACache<byte[]> implements IMap<String, T> {

	char Ctype = RedisConfig.CTypeBytes;
	final static MyKryo kryo = MyKryo.getInstance();
	IDo1<String> 项过期函数 = null;

	@SuppressWarnings("rawtypes")
	public static TimeCache getInstance(String alias) {
		return new TimeCache(alias);
	}

	private TimeCache(String alias) {
		RedisConfig cc = getConfig(alias);
		super.ini(cc);
	}

	/**
	 * map是固定的存储单元，
	 * 
	 * _queue 保存了当前有用的key ，
	 * 
	 * 不用的放在_可回收AC队列 中，
	 * 
	 * 这样不用二次实例化ACValue
	 * 
	 */
	private Map<String, ACValue<T>> _map = new ConcurrentHashMap<>();
	private ConcurrentLinkedQueue<ACValue<T>> _可回收AC队列 = new ConcurrentLinkedQueue<>();
	/**
	 * 回收队列 AC队列 Ex队列 都属于空间换时间 技术
	 * 
	 * AC队列 为了减少创建 ACValue的时间
	 * 
	 * Ex队列 为了减少创建 ExpValue的时间
	 */
	private RecExpQueue<ACValue<T>> _可回收EXP队列 = new RecExpQueue<>();
	int _maxexpire = 0;

	/**
	 * 如果map有键值 从tv中取出 值 调用以后，会延迟过期时间
	 */
	@Override
	public T get(String key) {
		if (MyFun.isEmpty(key))
			return null;
		ACValue<T> tv = null;
		if (_map.containsKey(key))
			tv = _map.get(key);
		else {
			byte[] temp = _rdcfg.getRedis().hget(_group, key.getBytes());
			if (temp == null)
				return null;
			// String s = new String(temp);
			// T value = (T) JSON.parseObject(s);
			T value = kryo.deserialize(temp);
			tv = new ACValue<T>(key, value);
			_map.put(key, tv);
		}
		if (tv == null)
			return null;
		_可回收EXP队列.add(tv);
		return tv.value();
	}

	/**
	 * 仅仅是取值，不会延迟过期时间
	 * 
	 * @return
	 */
	public T getValue(String key) {
		if (MyFun.isEmpty(key))
			return null;
		ACValue<T> tv = null;
		if (_map.containsKey(key))
			tv = _map.get(key);
		else {
			byte[] temp = _rdcfg.getRedis().hget(_group, key.getBytes());
			// String s = new String(temp);
			// T value = (T) JSON.parseObject(s);
			T value = kryo.deserialize(temp);
			tv = new ACValue<T>(key, value);
			_map.put(key, tv);
		}
		if (tv == null)
			return null;
		return tv.getValue();
	}

	public List<T> getAll() {
		List<T> list = new ArrayList<>();
		for (Entry<String, ACValue<T>> ab : _map.entrySet()) {
			list.add(ab.getValue().getValue());
		}
		return list;
	}

	/**
	 * 如果 存在该键 那么直接修改
	 * 
	 * 如果 不存在 看看有没有可回收的TimeValue
	 * 
	 * 如果没有实例化一个，如果有，用旧的改一下,复用TimeValue
	 * 
	 * 不重新实例化
	 */
	@Override
	public T put(String key, T value) {
		if (MyFun.isEmpty(key))
			return null;
		lock();
		try {
			ACValue<T> tv = _map.get(key);
			if (tv == null) {
				tv = _可回收AC队列.poll();
				if (tv == null)
					tv = new ACValue<T>(key, value);
				else {
					tv.resetTimes();
					tv.set(key, value);
				}
				_map.put(key, tv);
			} else {
				tv.setValue(value);

			}
			_可回收EXP队列.add(tv);
			if (_group == null)
				return null;

			byte[] bb = kryo.serialize(tv.getValue());
			_rdcfg.addRedisCommand(new Object[] { RedisConfig.Chset, Ctype, _group, key.getBytes(), bb });
			return tv.getValue();
		} finally {
			unlock();
		}
	
	}

	/**
	 * 如果 存在该键 那么TimeValue 先移到回收站，然后从map中移除 如果 不存在 退出
	 */
	@Override
	public T remove(String key) {
		if (MyFun.isEmpty(key))
			return null;
		lock();
		try {
			// System.out.println(" remove1 key="+key+"map> "+JSON.toJSONString(_map));
			ACValue<T> tv = _map.get(key);
			if (tv == null)
				return null;
			_可回收AC队列.offer(tv);
			_map.remove(key);
			if (_group == null)
				return null;
			_rdcfg.addRedisCommand(new Object[] { RedisConfig.Chdel, Ctype, _group, key.getBytes() });
			return tv.getValue();
		} finally {
			unlock();
		}
	}

	@Override
	public boolean containsKey(String key) {
		if (MyFun.isEmpty(key))
			return false;
		if (_map.containsKey(key))
			return true;
		else
			return _rdcfg.getRedis().hexists(_group, key.getBytes());
	}

	/**
	 * 从redis 读取所有key 写入内存 map
	 */
	@Override
	public void open(String group) {
		super.open(group);
		_group = group.getBytes();
		lock();
		try {
			Map<byte[], byte[]> map = _rdcfg.getRedis().hgetall(_group);
			if (map != null) {
				for (Entry<byte[], byte[]> ab : map.entrySet()) {
					String key = new String(ab.getKey());
					// T value = JSON.parseObject(ab.getValue(), Object.class);
					T value = kryo.deserialize(ab.getValue());
					if (value != null) {
						ACValue<T> tv = new ACValue<T>(key, value);
						//MyFun.printJson(tv);
						_map.put(key, tv);
						_可回收EXP队列.add(tv);
					}
				}
			}
		} finally {
			unlock();
		}
	}

	public void set项过期函数(IDo1<String> 过期函数) {
		this.项过期函数 = 过期函数;
	}

	/**
	 * 如果队列中存在元素
	 * 
	 * 如果当前元素 没有过期 不处理 直接跳出
	 * 
	 * 如果当前元素 已经过期了
	 * 
	 * 取出 同步销毁map中的同键元素
	 * 
	 * 有人使用,每多1人使用,就延长1个标准过期时间
	 * 
	 * 没有人使用 就删除该对象
	 * 
	 */
	@Override
	public void clear() {
		lock();
		try {
			// System.out.println(MyFun.getNow() + " clear " + _tque.toJson());

			while (!_可回收EXP队列.isEmpty()) {
				ExpValue<ACValue<T>> te = _可回收EXP队列.peek();

//				MyFun.print(te.getValue().getKey(), te.getValue().getTimes(), te.getStamp(), _expirems, _timestamp, ">",
//						(te.getStamp() + _expirems));
				//MyFun.print(te.getValue().getKey());

				if (te.getStamp() + _expirems < _timestamp) {

					_可回收EXP队列.poll();

					ACValue<T> tv = te.getValue();

					// 没有人使用
					if (tv.getTimes() < 1) {

						if (this.项过期函数 != null)
							this.项过期函数.run(tv.getKey());
						// 从map中移除

						remove(tv.getKey());

					} else {

						// 有人使用,每多1人使用,就延长1个标准过期时间
						tv.decOne();
						te.setStamp(_timestamp);

						// 不能用te。setStamp，必须新实例化一个，
						// 因为 te 是对象，全队列的同一对象会一起变
					}

				} else {
					break;

				}
				// System.out.println("--3--");
			}
			// System.out.println("--4--");
			_rdcfg.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			unlock();
		}
		// System.out.println();
	}

	@Override
	public void expire() {
		// 如果过期常数<=0 不处理
		if (_expirems <= 0)
			return;
		// 如果有过期常数，超过常数的进行处理
		_timestamp = MyFun.getMs();
		// 如果超期删除整组数据
		if (组过期函数 != null)
			组过期函数.run(_group);
		clear();

	}

}
